﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Serialization;
using EpiNEXT.Services.Fields;

namespace EpiNEXT.Services.Validation
{
    //[Serializable()]
    public sealed class SingleCondition : ConditionBase
    {
        private string _equalityOperatorString = "EqualTo";
        private EqualityOperators _equalityOperator = EqualityOperators.EqualTo;

        //[XmlElement]
        //public Behavior Condition { get; set; }

        [XmlIgnore]
        public EqualityOperators EqualityOperator
        {
            get { return this._equalityOperator; }
            set
            {
                if (EqualityOperator != value)
                {
                    this._equalityOperator = value;
                    switch (EqualityOperator)
                    {
                        case EqualityOperators.EqualTo:
                            EqualityOperatorString = "EqualTo";
                            break;
                        case EqualityOperators.GreaterThan:
                            EqualityOperatorString = "GreaterThan";
                            break;
                        case EqualityOperators.GreaterThanOrEqualTo:
                            EqualityOperatorString = "GreaterThanOrEqualTo";
                            break;
                        case EqualityOperators.LessThan:
                            EqualityOperatorString = "LessThan";
                            break;
                        case EqualityOperators.LessThanOrEqualTo:
                            EqualityOperatorString = "LessThanOrEqualTo";
                            break;
                        case EqualityOperators.NotEqualTo:
                            EqualityOperatorString = "NotEqualTo";
                            break;
                    }
                }
            }
        }

        [XmlAttribute("Operator")]
        public string EqualityOperatorString
        {
            get { return this._equalityOperatorString; }
            set
            {
                if (!EqualityOperatorString.Equals(value))
                {
                    this._equalityOperatorString = value;
                    switch (EqualityOperatorString)
                    {
                        case "EqualTo":
                            EqualityOperator = EqualityOperators.EqualTo;
                            break;
                        case "GreaterThan":
                            EqualityOperator = EqualityOperators.GreaterThan;
                            break;
                        case "GreaterThanOrEqualTo":
                            EqualityOperator = EqualityOperators.GreaterThanOrEqualTo;
                            break;
                        case "LessThan":
                            EqualityOperator = EqualityOperators.LessThan;
                            break;
                        case "LessThanOrEqualTo":
                            EqualityOperator = EqualityOperators.LessThanOrEqualTo;
                            break;
                        case "NotEqualTo":
                            EqualityOperator = EqualityOperators.NotEqualTo;
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// A list of expressions that comprise this condition
        /// </summary>
        [XmlArray("Tokens")]
        [XmlArrayItem("Token", typeof(Token))]
        public ObservableCollection<Token> Tokens { get; set; }

        public SingleCondition()
            : base()
        {

        }

        public override List<IField> GetFieldsInCondition(DataEntryInstrument form)
        {
            List<IField> fields = new List<IField>();

            if (this.Tokens != null)
            {
                foreach (Token token in this.Tokens)
                {
                    if (token.TokenType == TokenTypes.FieldName)
                    {
                        fields.Add(form.Fields[token.Value]);
                    }
                }
            }

            //if (this.Condition != null)
            //{
            //    fields.AddRange(Condition.GetFieldsInCondition(form));
            //}

            return fields;
        }

        public override bool Evaluate(Record record)
        {
            DataEntryInstrument form = record.Form;

            if (Tokens == null || Tokens.Count != 2)
            {
                // invalid token structure (throw exception??)
                return false;
            }
            else
            {
                // we have two tokens as expected and no inner condition to deal with
                IEnumerable<Token> tokenList = Tokens.AsEnumerable<Token>();
                Token token1 = tokenList.First();
                Token token2 = tokenList.Last();

                if (!IsValid(token1, token2))
                {
                    throw new InvalidOperationException();
                }
                else if (token1.TokenType == TokenTypes.FieldName && token2.TokenType == TokenTypes.FieldName)
                {
                    IField field1 = form.Fields[token1.Value];
                    IField field2 = form.Fields[token2.Value];

                    if (field1.DataType != field2.DataType)
                    {
                        throw new InvalidOperationException();
                    }
                }

                string dataType = "System.String";

                foreach (Token token in this.Tokens)
                {
                    if (token.TokenType == TokenTypes.FieldName)
                    {
                        IField field = form.Fields[token.Value];
                        dataType = field.DataType;
                        break;
                    }
                }

                object rawValue1 = token1.GetValue(record);
                object rawValue2 = token2.GetValue(record);

                if (rawValue1 == null || rawValue2 == null)
                {
                    return false;
                }

                switch (dataType)
                {
                    default:
                    case "System.String":
                        return CompareStrings(token1.GetValue(record).ToString(), token2.GetValue(record).ToString());
                    case "System.DateTime":
                        DateTime dt1 = new DateTime((long)rawValue1);
                        DateTime dt2 = new DateTime((long)rawValue2);
                        return CompareDates(dt1, dt2);
                    case "System.Boolean":
                        bool b1 = (bool)rawValue1;
                        bool b2 = (bool)rawValue2;
                        return CompareBooleans(b1, b2);
                    case "System.Decimal":
                        decimal d1 = (decimal)rawValue1;
                        decimal d2 = (decimal)rawValue2;
                        return CompareDecimals(d1, d2);
                }
            }
        }

        private bool CompareDecimals(decimal value1, decimal value2)
        {
            switch (EqualityOperator)
            {
                default:
                case EqualityOperators.EqualTo:
                    return (value1 == value2);
                case EqualityOperators.GreaterThan:
                    return (value1 > value2);
                case EqualityOperators.GreaterThanOrEqualTo:
                    return (value1 >= value2);
                case EqualityOperators.LessThan:
                    return (value1 < value2);
                case EqualityOperators.LessThanOrEqualTo:
                    return (value1 <= value2);
                case EqualityOperators.NotEqualTo:
                    return (value1 != value2);
            }
        }

        private bool CompareDates(DateTime value1, DateTime value2)
        {
            switch (EqualityOperator)
            {
                default:
                case EqualityOperators.EqualTo:
                    return (value1 == value2);
                case EqualityOperators.GreaterThan:
                    return (value1 > value2);
                case EqualityOperators.GreaterThanOrEqualTo:
                    return (value1 >= value2);
                case EqualityOperators.LessThan:
                    return (value1 < value2);
                case EqualityOperators.LessThanOrEqualTo:
                    return (value1 <= value2);
                case EqualityOperators.NotEqualTo:
                    return (value1 != value2);
            }
        }

        private bool CompareStrings(string value1, string value2)
        {
            switch (EqualityOperator)
            {
                default:
                case EqualityOperators.EqualTo:
                    return value1.Equals(value2, StringComparison.OrdinalIgnoreCase);
                case EqualityOperators.GreaterThan:
                    throw new NotImplementedException();
                case EqualityOperators.GreaterThanOrEqualTo:
                    throw new NotImplementedException();
                case EqualityOperators.LessThan:
                    throw new NotImplementedException();
                case EqualityOperators.LessThanOrEqualTo:
                    throw new NotImplementedException();
                case EqualityOperators.NotEqualTo:
                    return (!value1.Equals(value2, StringComparison.OrdinalIgnoreCase));
            }
        }

        private bool CompareBooleans(bool value1, bool value2)
        {
            switch (EqualityOperator)
            {
                default:
                case EqualityOperators.EqualTo:
                    return value1.Equals(value2);
                case EqualityOperators.GreaterThan:
                    throw new NotImplementedException();
                case EqualityOperators.GreaterThanOrEqualTo:
                    throw new NotImplementedException();
                case EqualityOperators.LessThan:
                    throw new NotImplementedException();
                case EqualityOperators.LessThanOrEqualTo:
                    throw new NotImplementedException();
                case EqualityOperators.NotEqualTo:
                    return (!value1.Equals(value2));
            }
        }

        private bool IsValid(Token token1, Token token2)
        {
            if(token1.TokenType == TokenTypes.Literal && token2.TokenType == TokenTypes.Literal) 
            {
                return false;
            }

            return true;
        }
    }
}
